export class XMLError extends Error {
    line;
    constructor(line, message) {
        super(`line ${line + 1}: ${message}`);
        this.line = line;
    }
}



export const  parseXML = (data) => {
    const WHITE_SPACES = [' ', '\r', '\n', '\t'];
    //当前位置
    let cur = 0;
    let line = 0;
    let xmlProps = [];
    //前进
    function forward(n = 1) {
        for (let i = 0; i < n; ++i) {
            ++cur;
            if (data[cur] == '\n')
                ++line;
        }
    }
    //跳过空白
    function skipWhite() {
        while (WHITE_SPACES.includes(data[cur]))
            forward();
    }
    //EOF检测
    function checkEOF() {
        if (!data[cur])
            throw new XMLError(line, 'xml EOF');
    }
    //读取字符串
    function readString() {
        forward();
        let beg = cur;
        while (true) {
            checkEOF();
            if (data[cur] === '"') {
                const result = data.substring(beg, cur);
                forward();
                return result;
            }
            forward();
        }
    }
    //读取名称
    function readName(endChars) {
        let beg = cur;
        while (true) {
            checkEOF();
            if (endChars.includes(data[cur]))
                return data.substring(beg, cur).trim();
            forward();
        }
    }
    //读取标签属性
    function parseProps() {
        const props = [];
        while (true) {
            checkEOF();
            if ([...WHITE_SPACES, '/', '>', '?'].includes(data[cur]))
                break; //结束
            //读取名称
            skipWhite();
            const name = readName(['=', ...WHITE_SPACES, '/', '>', '?']);
            if (!name)
                throw new Error(`Expected TAG name`);
            skipWhite();
            //等号
            if (data[cur] == '=') {
                forward();
                skipWhite();
                const value = readString();
                props.push({ name, value });
                skipWhite();
            }
            //没有值
            else
                props.push({ name });
        }
        return props;
    }
    // 解析 <?xml
    function parseXMLTag() {
        forward(2);
        if (data.substring(cur, cur + 4) != 'xml ')
            throw new XMLError(line, `xml tag must start whit "<?xml "`);
        forward(4);
        xmlProps = parseProps();
        skipWhite();
        if (data[cur] != '?' && data[cur + 1] != '>')
            throw new XMLError(line, 'xml tag must end with ?>');
        forward(2);
        skipWhite();
        //接着需要是标签
        if (data[cur] != '<')
            throw new Error('TAG expected');
    }
    function parseTag() {
        forward();
        //标签名称
        const name = readName([...WHITE_SPACES, '/', '>']);
        skipWhite();
        //属性
        const props = parseProps();
        skipWhite();
        let children = undefined;
        //结束判断
        if (data[cur] == '/') {
            if (data[cur + 1] != '>')
                throw new XMLError(line, `"/>" expected`);
            forward(2);
        }
        //读取子内容
        else if (data[cur] == '>') {
            forward();
            skipWhite();
            //存在子标签
            if (data[cur] === '<' && data[cur + 1] != '/') {
                children = [];
                while (data[cur] === '<' && data[cur + 1] != '/') {
                    children.push(parseTag());
                    skipWhite();
                }
            }
            //直接就是文本
            else
                children = parseContent();
            //结束标签
            if (data[cur] != '<' && data[cur] != '/')
                throw new XMLError(line, `"</" expected`);
            forward(2);
            skipWhite();
            const end = readName(['>']).trim();
            if (end != name)
                throw new XMLError(line, `tag ${name} not match to ${end}`);
            skipWhite();
            forward(1);
        }
        //错误
        else
            throw new Error(`">" or "/>" expected`);
        skipWhite();
        return { name, props, children };
    }
    function parseContent() {
        let beg = cur;
        while (true) {
            checkEOF();
            if (data[cur] == '<')
                break;
            forward();
        }
        return data.substring(beg, cur).trim();
    }
    //解析内容
    skipWhite();
    if (data[cur + 1] == '?')
        parseXMLTag();
    return {
        props: xmlProps,
        root: parseTag(),
    };
}

